﻿using Tesla.Bounding;
using Tesla.Content;
using Tesla.Core;
using Tesla.Graphics;
using Tesla.Input;
using Tesla.Math;
using Tesla.Scene;
using Tesla.Scene.Shape;

namespace TeslaSamples.Bounding {
    [AppDescription("Ray Picking", PlatformCapabilities.Direct3D10 | PlatformCapabilities.XNA4, "RayPickingImage", "RayPickingDesc", "RayPickingSource")]
    public class RayPicking : BasicApp {
        private PickQuery query;
        private Mesh picks;
        private Line pickRay;

        protected override void LoadContent() {
            Window.Title = "Ray Picking Sample";
            //Load a model that we'll use to pick
            ModelLoaderParameters mlp = new ModelLoaderParameters();
            mlp.SwapWindingOrder = true;
            mlp.NormalGeneration = NormalGeneration.Crease;
            mlp.PreferLitMaterials = true;

            Spatial jeep = ContentManager.Load<Spatial>("Models//Jeep.dxs", mlp);
            //jeep.Translation = new Vector3(0, -50, 0);
            jeep.Rotation = Quaternion.FromAngleAxis(MathHelper.ToRadians(140), Vector3.UnitY);
            jeep.SetModelBound(new BoundingBox());
            RootNode.AddChild(jeep);

            //Adjust the default camera a tad
            Renderer.CurrentCamera.Position = new Vector3(0, 100, 100);
            Renderer.CurrentCamera.LookAt(jeep.Translation, Vector3.Up);
            Renderer.CurrentCamera.Update();

            //Set the mouse visible so we can see the cursor
            Window.IsMouseVisible = true;

            //Create our query
            query = new PickQuery();
            //We want to enable primitive picking to get triangle accurate results, when we do the intersection tests,
            //the utility will first do a board bounding volume test, and if that succeeds then a finer primitive test.
            query.EnablePrimitivePicking = true; 

            //Setup out input - whenever we left click, we'll cast a ray into the scene
            InputLayer.RegisterTrigger(new InputTrigger(new InputCondition(delegate(GameTime time) {
                MouseState ms = Mouse.GetMouseState();
                if(ms.LeftButton == ButtonState.Pressed) {
                    return true;
                }
                return false;
            }), new InputAction(delegate(GameTime time) {
                MouseState ms = Mouse.GetMouseState();
                query.Clear();

                //The camera class has easy-built in functionality to quickly create a pick ray where the origin
                //of the ray is on the near-plane of the view frustum
                PickingUtil.FindPick(RootNode, Renderer.CurrentCamera.CreatePickRay(ms.Position), query);
                CreatePicks(query);
            })));
        }

        private void CreatePicks(PickQuery query) {
            if(query.Count == 0) {
                return;
            }
            query.Sort();

            //Lazily create the mesh that holds the pick visuals, for this example we're only doing the closest picked
            //triangle
            if(picks == null) {
                picks = new Mesh("Picks");
                picks.Material = ContentManager.Load<Material>("Materials//BasicColor.tem").Clone();
                picks.Material.SetParameter("DiffuseColor", Color.Yellow);
                picks.SetRenderState(RasterizerState.CullNoneWireframe);
                picks.SetRenderState(DepthStencilState.None);
                picks.MeshData.Positions = new DataBuffer<Vector3>(3);
                picks.MeshData.UseIndexedPrimitives = false;
                picks.MeshData.Reconstruct();
                picks.SceneHints.RenderBucketType = RenderBucketType.PostBucket;
                picks.SceneHints.PickingHint = PickingHint.None;
                RootNode.AddChild(picks);

                pickRay = new Line("PickRay");
                pickRay.Material = ContentManager.Load<Material>("Materials//BasicVertColor.tem").Clone();
                RootNode.AddChild(pickRay);
            }

            DataBuffer<Vector3> db = picks.MeshData.Positions;
            db.Position = 0;
            PickResult results = query.ClosestPick.Value;
            Ray ray = results.SourceRay;

            //Get the closest primitive intersection record, and the closest intersection triangle (a ray may pass through many
            //triangles, we want the one closest to the ray's origin). These should exist, if we have results.
            IntersectionRecord record = results.PrimitiveIntersectionRecord.Value.ClosestIntersection.Value;
            Triangle tri = record.Triangle.Value;
            db.Set(tri.PointA);
            db.Set(tri.PointB);
            db.Set(tri.PointC);

            //Set the pick ray so we can see it on the screen
            pickRay.SetLine(ray.Origin, ray.Origin + ray.Direction * (record.Distance + 100), Color.Orange, Color.Orange);

            //Update the pick result with the triangle data
            picks.MeshData.UpdateVertexData<Vector3>(VertexSemantic.Position, db);
        }
    }
}
